package com.wl.common.mybatis.mybatis;

import com.baomidou.mybatisplus.core.conditions.AbstractWrapper;
import com.baomidou.mybatisplus.core.conditions.ISqlSegment;
import com.baomidou.mybatisplus.core.conditions.segments.NormalSegmentList;
import com.baomidou.mybatisplus.core.enums.SqlLike;
import com.baomidou.mybatisplus.core.toolkit.sql.SqlUtils;
import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.springframework.cglib.beans.BeanMap;
import org.springframework.util.ReflectionUtils;

import java.lang.reflect.Field;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.regex.Pattern;

/**
 * @author: wanglin
 * @date: 2023-07-12 周三
 * @Version: 1.0
 * @Description:
 */
public class CustomInnerInterceptor implements InnerInterceptor {
    /**
     * 正则表达式，匹配like ?
     */
    private static final Pattern PATTERN_LIKE = Pattern.compile("\\s+like\\s+$");
    /**
     * 正则表达式，匹配like concat('%',?,'%')
     */
    private static final Pattern PATTERN_LIKE_CONCAT = Pattern.compile("\\s+like\\s+concat\\s*\\(\\s*\\'\\%\\'\\s*\\,\\s*$");

    @Override
    public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds
            rowBounds, ResultHandler
                                    resultHandler, BoundSql boundSql) throws SQLException {
        modifyLikeSql(boundSql.getSql(), parameter, boundSql);
    }

    private void modifyLikeSql(String sql, Object parameter, BoundSql boundSql) {
        if (!(parameter instanceof HashMap)) {
            return;
        }

        if (!sql.toLowerCase().contains(" like ") || !sql.toLowerCase().contains("?")) {
            return;
        }

        // 获取关键字的个数
        String[] sqlSplitList = sql.split("\\?");
        List<String> parameterKeys = Lists.newArrayList();
        Boolean useQueryWrapper = false;

        for (int i = 0; i < sqlSplitList.length; i++) {
            String conditionSql = StringUtils.lowerCase(sqlSplitList[i]);
            if (PATTERN_LIKE.matcher(conditionSql).find() || PATTERN_LIKE_CONCAT.matcher(conditionSql).find()) {
                String parameterKey = boundSql.getParameterMappings().get(i).getProperty();
                parameterKeys.add(parameterKey);
                // 判断是否使用了QueryWrapper
                if (parameterKey.contains("ew.paramNameValuePairs.")) {
                    useQueryWrapper = true;
                }
            }
        }

        HashMap parameterMap = (HashMap) parameter;

        // 针对QueryWrapper，需要考虑like，leftLike，rightLike三种情况
        AbstractWrapper wrapper = null;
        List<SqlLike> wrapperLikeMode = Lists.newArrayList();
        List<Object> wrapperLikeValue = Lists.newArrayList();
        if (useQueryWrapper) {
            wrapper = (AbstractWrapper) parameterMap.get("ew");
            this.fetchLikeModeAndValue(wrapper, wrapperLikeMode, wrapperLikeValue);
        }

        // 对关键字进行特殊字符“清洗”，如果有特殊字符的，在特殊字符前添加转义字符（\）
        int wrapperLikeParamIndex = 0;
        for (String parameterKey : parameterKeys) {
            if (parameterKey.contains("ew.paramNameValuePairs.")) {
                // 第一种情况：QueryWrapper传参
                Map<String, Object> paramNameValuePairs = wrapper.getParamNameValuePairs();
                String[] keys = parameterKey.split("\\.");
                String key = keys[2];

                Object value = wrapperLikeValue.get(wrapperLikeParamIndex);
                if (this.needEscape(value)) {
                    SqlLike sqlLike = wrapperLikeMode.get(wrapperLikeParamIndex);
                    paramNameValuePairs.put(key, SqlUtils.concatLike(this.escape(value), sqlLike));
                }

                wrapperLikeParamIndex++;
            } else if (boundSql.hasAdditionalParameter(parameterKey)) {
                Object value = boundSql.getAdditionalParameter(parameterKey);
                if (this.needEscape(value)) {
                    boundSql.setAdditionalParameter(parameterKey, this.escape(value));
                }
            } else if (parameterKey.contains(".")) {
                // 第二种情况：通过对象传参
                Object value = null;
                String[] keys = parameterKey.split("\\.");
                Map<String, Object> map = Maps.newHashMap();
                for (int i = 0; i < keys.length; i++) {
                    if (i == 0) {
                        map = BeanMap.create(parameterMap.get(keys[i]));
                    } else if (i == keys.length - 1) {
                        value = map.get(keys[i]);
                        if (this.needEscape(value)) {
                            map.put(keys[i], this.escape(value));
                        }
                    } else {
                        map = BeanMap.create(map.get(keys[i]));
                    }
                }
            } else {
                // 第三种情况：普通传参
                Object value = parameterMap.get(parameterKey);
                if (this.needEscape(value)) {
                    parameterMap.put(parameterKey, this.escape(value));
                }
            }
        }
    }

    private void fetchLikeModeAndValue(AbstractWrapper
                                               wrapper, List<SqlLike> wrapperLikeMode, List<Object> wrapperLikeValue) {
        NormalSegmentList normalSegmentList = wrapper.getExpression().getNormal();
        for (ISqlSegment iSqlSegment : normalSegmentList) {
            if (iSqlSegment instanceof AbstractWrapper) {
                this.fetchLikeModeAndValue((AbstractWrapper) iSqlSegment, wrapperLikeMode, wrapperLikeValue);
                continue;
            }
            Field sqlLikeField = ReflectionUtils.findField(iSqlSegment.getClass(), "arg$3", SqlLike.class);
            if (Objects.isNull(sqlLikeField)) {
                continue;
            }
            ReflectionUtils.makeAccessible(sqlLikeField);
            SqlLike sqlLike = (SqlLike) ReflectionUtils.getField(sqlLikeField, iSqlSegment);
            Field valueField = ReflectionUtils.findField(iSqlSegment.getClass(), "arg$2");
            wrapperLikeMode.add(sqlLike);

            ReflectionUtils.makeAccessible(valueField);
            Object value = ReflectionUtils.getField(valueField, iSqlSegment);
            wrapperLikeValue.add(value);
        }
    }

    private String escape(Object value) {
        String str = Objects.toString(value, null);
        if (StringUtils.isNotBlank(str)) {
            str = str.replaceAll("\\\\", "\\\\\\\\");
            str = str.replaceAll("_", "\\\\_");
            str = str.replaceAll("%", "\\\\%");
        }
        return str;
    }

    private Boolean needEscape(Object value) {
        return value instanceof String
                && (value.toString().contains("_") || value.toString().contains("\\") || value.toString().contains("%"));
    }
}
